//*** Guidance generated code ***//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using ProjectBase.Core;

using $prjsn$.Entity;
using $prjsn$.Common;
using $prjsn$.Common.Validation;
using $prjsn$.Common.Business;

namespace $safeprojectname$
{
    public class AdditionalRulesReflector<TEntity, TFilter, TField> : IService
        where TEntity : BusinessObject
        where TFilter : BusinessObjectFilter, new()
        where TField : class, IField
    {
        public virtual void BuildRulesForAdditional(IList<MappingDescription> mappingInfo, TField field, List<IValidationRule<TEntity>> rules)
        {
            // NOTE expecting Exactly one IDictionary Additional
            MappingDescription mapping = mappingInfo
                .FirstOrDefault(m => m.IsDynamic);

            var dynamicDescription = mapping
                .DynamicComponents
                .FirstOrDefault(dc => dc.PropertyName.IsEqual(field.PropertyName));

            if (dynamicDescription.IsNull())
            {
                //throw new ArgumentException("The {0} field has Additional PropertyName: '{1}', which has missing persistence mapping "
                //    .FormatWith(typeof(TEntity).Name, field.PropertyName));
                return;
            }

            // Check uniqueness if required
            if (field.IsUnique)
            {
                var additionalProperty = mapping.PropertyName;
                var uniqueRule = new UniqueAdditionalPropertyRule<TEntity, int, TFilter>(additionalProperty, field);

                if (uniqueRule.Is())
                {
                    rules.Add(uniqueRule);
                }
            }

            CreateAdditionalFieldRules(field, mapping.PropertyInfo, dynamicDescription, rules);
        }

        #region Rules for Additional

        protected virtual void CreateAdditionalFieldRules(TField field, PropertyInfo property
            , IMappingDescription dynamicDescription, IList<IValidationRule<TEntity>> rules)
        {
            IValidationRule<TEntity> rule;
            var isProcessed = TryCreateAdditionalStringRule(field, dynamicDescription, property, out rule)
                              || TryCreateAdditonalValueTypeRule<int>(field, dynamicDescription, property, out rule)
                              || TryCreateAdditonalValueTypeRule<decimal>(field, dynamicDescription, property, out rule)
                              || TryCreateAdditonalValueTypeRule<bool>(field, dynamicDescription, property, out rule)
                              || TryCreateAdditonalValueTypeRule<DateTime>(field, dynamicDescription, property, out rule)
                              || TryCreateAdditionalCustomListRule(field, property, out rule);

            if (isProcessed)
            {
                rules.Add(rule);
                return;
            }

            TryCreateAdditionalPersistentRule(field, property, rules);
        }

        protected virtual void TryCreateAdditionalPersistentRule(TField field, PropertyInfo property, IList<IValidationRule<TEntity>> rules)
        {
            var isPersistent = field.Type.HasType<IPersistentObject>();

            if (isPersistent)
            {
            }
        }

        protected virtual bool TryCreateAdditionalCustomListRule(TField field, PropertyInfo property, out IValidationRule<TEntity> rule)
        {
            var isCustomList = field.Type.HasType<CustomList>();

            if (isCustomList)
            {
                rule = new AdditionalCustomListRule<TEntity>(field.SubType, field);
                return true;
            }

            rule = null;
            return false;
        }

        protected virtual bool TryCreateAdditionalStringRule(TField field, IMappingDescription dynamicDescription, PropertyInfo property, out IValidationRule<TEntity> rule)
        {
            var isString = field.Type.HasType<string>();

            if (!isString)
            {
                //if (dynamicDescription.PropertyType.HasType<string>())
                //{
                //    throw new ArgumentException("Inconsistent settings. The {0} field has Additional PropertyName: '{1}', which has type mapped as string, while the IFiled is {2}".FormatWith(typeof(TEntity).Name, field.PropertyName, field.DataType));
                //}
                rule = null;
                return false;
            }

            var newRule = new AdditionalStringRule<TEntity>(
                entity => (property.GetValue(entity, null) as IDictionary)[field.PropertyName] as string
                , field);

            // is Required
            if (field.IsRequired)
            {
                newRule.AsRequired();
            }

            // is there Lower Length
            int min;
            if (field.MinValue.IsNotEmpty()
                && field.MinValue.TryParse(out min))
            {
                newRule.RequireMinLength(min);
            }

            // is there Upper Length
            int max = int.MaxValue;
            if (field.MaxValue.IsNotEmpty() || field.Length > 0)
            {
                if (!field.MaxValue.TryParse(out max))
                {
                    max = field.Length.GetValueOrDefault(20000000);
                }
            }
            newRule.RequireMaxLength(max);

            rule = newRule;
            return true;
        }

        protected virtual bool TryCreateAdditonalValueTypeRule<TValueType>(TField field, IMappingDescription dynamicDescription, PropertyInfo property, out IValidationRule<TEntity> rule)
            where TValueType : struct, IComparable<TValueType>
        {
            var isValueType = field.Type.IsEquivalentTo(typeof(TValueType))
                || field.Type.IsEquivalentTo(typeof(TValueType?));

            if (!isValueType)
            {
                rule = null;
                return false;
            }
            var mappedType = dynamicDescription.PropertyType;

            isValueType = mappedType.IsEquivalentTo(typeof(TValueType))
                || mappedType.IsEquivalentTo(typeof(TValueType?));

            if (!isValueType)
            {
                //throw new ArgumentException("Inconsistent settings. The {0} field has Additional PropertyName: '{1}', which has type mapped as '{3}', while the IFiled is '{2}'".FormatWith(typeof(TEntity).Name, field.PropertyName, field.DataType, mappedType.Name));
                rule = null;
                return true;
            }

            var newRule = new AdditionalValueTypeRule<TEntity, TValueType>(
                entity => (property.GetValue(entity, null) as IDictionary)[field.PropertyName] as TValueType?
                , field);

            // is Required
            if (field.IsRequired)
            {
                newRule.AsRequired();
            }

            // is there Lower Limit
            var min = default(TValueType);
            if (field.MinValue.IsEmpty()
                || !field.MinValue.TryParse(out min))
            {
                min = min.MinValue();
            }

            newRule.RequireMinValue(min);

            // is there Upper Limit
            var max = default(TValueType);
            if (field.MaxValue.IsEmpty()
                || !field.MaxValue.TryParse(out max))
            {
                max = max.MaxValue();
            }

            newRule.RequireMaxValue(max);

            rule = newRule;
            return true;
        }

        #endregion Rules for Additional

        #region properties

        protected virtual BusinessObjectTypes BusinessObjectType { get; set; }

        public virtual IFieldObjectFacade<TField> FieldFacade { protected get; set; }

        #endregion properties
    }
}